Resource Actions

Overview

Argo CD allows operators to define custom actions which users can perform on specific resource types. This is used internally to provide actions like restart for a DaemonSet, or retry for an Argo Rollout.

Operators can add actions to custom resources in form of a Lua script and expand those capabilities.

Custom Resource Actions

Argo CD supports custom resource actions written in Lua. This is useful if you:

  • Have a custom resource for which Argo CD does not provide any built-in actions.
  • Have a commonly performed manual task that might be error prone if executed by users via kubectl

The resource actions act on a single object.

You can define your own custom resource actions in the argocd-cm ConfigMap.

Custom Resource Action Types

An action that modifies the source resource

This action modifies and returns the source resource. This kind of action was the only one available till 2.8, and it is still supported.

An action that produces a list of new or modified resources

An alpha feature, introduced in 2.8.

This action returns a list of impacted resources, each impacted resource has a K8S resource and an operation to perform on.
Currently supported operations are “create” and “patch”, “patch” is only supported for the source resource.
Creating new resources is possible, by specifying a “create” operation for each such resource in the returned list.
One of the returned resources can be the modified source object, with a “patch” operation, if needed.
See the definition examples below.

Define a Custom Resource Action in argocd-cm ConfigMap

Custom resource actions can be defined in resource.customizations.actions.<group_kind> field of argocd-cm. Following example demonstrates a set of custom actions for CronJob resources, each such action returns the modified CronJob. The customizations key is in the format of resource.customizations.actions.<apiGroup_Kind>.

  1. resource.customizations.actions.batch_CronJob: |
  2. discovery.lua: |
  3. actions = {}
  4. actions["suspend"] = {["disabled"] = true}
  5. actions["resume"] = {["disabled"] = true}
  6. local suspend = false
  7. if obj.spec.suspend ~= nil then
  8. suspend = obj.spec.suspend
  9. end
  10. if suspend then
  11. actions["resume"]["disabled"] = false
  12. else
  13. actions["suspend"]["disabled"] = false
  14. end
  15. return actions
  16. definitions:
  17. - name: suspend
  18. action.lua: |
  19. obj.spec.suspend = true
  20. return obj
  21. - name: resume
  22. action.lua: |
  23. if obj.spec.suspend ~= nil and obj.spec.suspend then
  24. obj.spec.suspend = false
  25. end
  26. return obj

The discovery.lua script must return a table where the key name represents the action name. You can optionally include logic to enable or disable certain actions based on the current object state.

Each action name must be represented in the list of definitions with an accompanying action.lua script to control the resource modifications. The obj is a global variable which contains the resource. Each action script returns an optionally modified version of the resource. In this example, we are simply setting .spec.suspend to either true or false.

Creating new resources with a custom action

Important

Creating resources via the Argo CD UI is an intentional, strategic departure from GitOps principles. We recommend that you use this feature sparingly and only for resources that are not part of the desired state of the application.

The resource the action is invoked on would be referred to as the source resource.
The new resource and all the resources implicitly created as a result, must be permitted on the AppProject level, otherwise the creation will fail.

Creating a source resource child resources with a custom action

If the new resource represents a k8s child of the source resource, the source resource ownerReference must be set on the new resource.
Here is an example Lua snippet, that takes care of constructing a Job resource that is a child of a source CronJob resource - the obj is a global variable, which contains the source resource:

  1. -- ...
  2. ownerRef = {}
  3. ownerRef.apiVersion = obj.apiVersion
  4. ownerRef.kind = obj.kind
  5. ownerRef.name = obj.metadata.name
  6. ownerRef.uid = obj.metadata.uid
  7. job = {}
  8. job.metadata = {}
  9. job.metadata.ownerReferences = {}
  10. job.metadata.ownerReferences[1] = ownerRef
  11. -- ...
Creating independent child resources with a custom action

If the new resource is independent of the source resource, the default behavior of such new resource is that it is not known by the App of the source resource (as it is not part of the desired state and does not have an ownerReference).
To make the App aware of the new resource, the app.kubernetes.io/instance label (or other ArgoCD tracking label, if configured) must be set on the resource.
It can be copied from the source resource, like this:

  1. -- ...
  2. newObj = {}
  3. newObj.metadata = {}
  4. newObj.metadata.labels = {}
  5. newObj.metadata.labels["app.kubernetes.io/instance"] = obj.metadata.labels["app.kubernetes.io/instance"]
  6. -- ...

While the new resource will be part of the App with the tracking label in place, it will be immediately deleted if auto prune is set on the App.
To keep the resource, set Prune=false annotation on the resource, with this Lua snippet:

  1. -- ...
  2. newObj.metadata.annotations = {}
  3. newObj.metadata.annotations["argocd.argoproj.io/sync-options"] = "Prune=false"
  4. -- ...

(If setting Prune=false behavior, the resource will not be deleted upon the deletion of the App, and will require a manual cleanup).

The resource and the App will now appear out of sync - which is the expected ArgoCD behavior upon creating a resource that is not part of the desired state.

If you wish to treat such an App as a synced one, add the following resource annotation in Lua code:

  1. -- ...
  2. newObj.metadata.annotations["argocd.argoproj.io/compare-options"] = "IgnoreExtraneous"
  3. -- ...

An action that produces a list of resources - a complete example:

  1. resource.customizations.actions.ConfigMap: |
  2. discovery.lua: |
  3. actions = {}
  4. actions["do-things"] = {}
  5. return actions
  6. definitions:
  7. - name: do-things
  8. action.lua: |
  9. -- Create a new ConfigMap
  10. cm1 = {}
  11. cm1.apiVersion = "v1"
  12. cm1.kind = "ConfigMap"
  13. cm1.metadata = {}
  14. cm1.metadata.name = "cm1"
  15. cm1.metadata.namespace = obj.metadata.namespace
  16. cm1.metadata.labels = {}
  17. -- Copy ArgoCD tracking label so that the resource is recognized by the App
  18. cm1.metadata.labels["app.kubernetes.io/instance"] = obj.metadata.labels["app.kubernetes.io/instance"]
  19. cm1.metadata.annotations = {}
  20. -- For Apps with auto-prune, set the prune false on the resource, so it does not get deleted
  21. cm1.metadata.annotations["argocd.argoproj.io/sync-options"] = "Prune=false"
  22. -- Keep the App synced even though it has a resource that is not in Git
  23. cm1.metadata.annotations["argocd.argoproj.io/compare-options"] = "IgnoreExtraneous"
  24. cm1.data = {}
  25. cm1.data.myKey1 = "myValue1"
  26. impactedResource1 = {}
  27. impactedResource1.operation = "create"
  28. impactedResource1.resource = cm1
  29. -- Patch the original cm
  30. obj.metadata.labels["aKey"] = "aValue"
  31. impactedResource2 = {}
  32. impactedResource2.operation = "patch"
  33. impactedResource2.resource = obj
  34. result = {}
  35. result[1] = impactedResource1
  36. result[2] = impactedResource2
  37. return result